﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
#endregion

namespace TheSpirit
{
    public class Character
    {
        #region Declaration

        string name;

        int attack;
        int mana;
        int level;
        int velocity;

        bool isSelected;
        bool isChangeRoute;
        bool isTraceEnemy;
        bool isMove;
        bool right;
        bool left;
        bool up;
        bool down;

        //좌표(전체)
        Point worldLocation;
        //좌표(화면상)
        Point localLocation;
        //좌표(타일)
        Point tileLocation;
        //캐릭터 이동 시 목적지 저장
        Point destinationTile;

        Attribute attribute;
        Animation animation;
        Enemy target;
        //캐릭터 이동 시 이동 루트를 저장
        List<Tile> parent = new List<Tile>();

        ContentManager content;

        #endregion


        #region Properties

        public bool IsSelected
        {
            set { isSelected = value; }
            get { return isSelected; }
        }
        public int Attack
        {
            set { attack = value; }
            get { return attack; }
        }
        public int Mana
        {
            set { mana = value; }
            get { return mana; }
        }
        public int Level
        {
            get { return level; }
        }
        public string Name
        {
            get { return name; }
        }
        public Point WorldLocation
        {
            set { worldLocation = value; }
            get { return worldLocation; }
        }
        public Point LocalLocation
        {
            set { localLocation = value; }
            get { return localLocation; }
        }
        public Attribute CharacterAttr
        {
            get { return attribute; }
        }

        #endregion


        #region Initialize

        public Character(int tileX, int tileY)
        {
            name = "Fire";

            Attack = 10;
            Mana = 0;
            level = 1;
            velocity = 2;

            isSelected = false;
            isChangeRoute = false;
            isTraceEnemy = false;
            isMove = false;
            right = false;
            left = false;
            up = false;
            down = false;

            worldLocation = new Point(0, 0);
            localLocation = new Point(0, 0);
            tileLocation = new Point(tileX, tileY);
            destinationTile = new Point(tileX, tileY);

            animation = new Animation();
            attribute = new Attribute();
        }

        public void LoadContent(ContentManager content)
        {
            this.content = content;

            SelectCharacter("Water");

            switch(attribute.attributeType)
            {
                case Attribute.AttributeType.Fire:
                    name = "Fire";

                    animation.AddAnimation("FireFront1", new AnimationStrip(content.Load<Texture2D>("Character/Fire/FireFront1"), 100, "FireFront1"));
                    animation.animations["FireFront1"].Setting(true, 0.3f);
                    animation.AddAnimation("FireBack1", new AnimationStrip(content.Load<Texture2D>("Character/Fire/FireBack1"), 100, "FireBack1"));
                    animation.animations["FireBack1"].Setting(true, 0.3f);
                    animation.AddAnimation("FireSide1", new AnimationStrip(content.Load<Texture2D>("Character/Fire/FireSide1"), 100, "FireSide1"));
                    animation.animations["FireSide1"].Setting(true, 0.3f);
                    animation.AddAnimation("FireFrontAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Fire/FireFrontAttack1"), 100, "FireFrontAttack1"));
                    animation.animations["FireFrontAttack1"].Setting(true, 0.3f);
                    animation.AddAnimation("FireSideAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Fire/FireSideAttack1"), 100, "FireSideAttack1"));
                    animation.animations["FireSideAttack1"].Setting(true, 0.3f);
                    animation.AddAnimation("FireBackAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Fire/FireBackAttack1"), 100, "FireBackAttack1"));
                    animation.animations["FireBackAttack1"].Setting(true, 0.3f);

                    break;
                case Attribute.AttributeType.Water:
                    name = "Water";

                    animation.AddAnimation("WaterFront1", new AnimationStrip(content.Load<Texture2D>("Character/Water/WaterFront1"), 100, "WaterFront1"));
                    animation.animations["WaterFront1"].Setting(true, 0.3f);
                    animation.AddAnimation("WaterBack1", new AnimationStrip(content.Load<Texture2D>("Character/Water/WaterBack1"), 100, "WaterBack1"));
                    animation.animations["WaterBack1"].Setting(true, 0.3f);
                    animation.AddAnimation("WaterSide1", new AnimationStrip(content.Load<Texture2D>("Character/Water/WaterSide1"), 100, "WaterSide1"));
                    animation.animations["WaterSide1"].Setting(true, 0.3f);
                    animation.AddAnimation("WaterFrontAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Water/WaterFrontAttack1"), 100, "WaterFrontAttack1"));
                    animation.animations["WaterFrontAttack1"].Setting(true, 0.3f);
                    animation.AddAnimation("WaterSideAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Water/WaterSideAttack1"), 100, "WaterSideAttack1"));
                    animation.animations["WaterSideAttack1"].Setting(true, 0.3f);
                    animation.AddAnimation("WaterBackAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Water/WaterBackAttack1"), 100, "WaterBackAttack1"));
                    animation.animations["WaterBackAttack1"].Setting(true, 0.3f);

                    break;
                case Attribute.AttributeType.Forest:
                    name = "Forest";

                    animation.AddAnimation("ForestFront1", new AnimationStrip(content.Load<Texture2D>("Character/Forest/ForestFront1"), 100, "ForestFront1"));
                    animation.animations["ForestFront1"].Setting(true, 0.3f);
                    animation.AddAnimation("ForestBack1", new AnimationStrip(content.Load<Texture2D>("Character/Forest/ForestBack1"), 100, "ForestBack1"));
                    animation.animations["ForestBack1"].Setting(true, 0.3f);
                    animation.AddAnimation("ForestSide1", new AnimationStrip(content.Load<Texture2D>("Character/Forest/ForestSide1"), 100, "ForestSide1"));
                    animation.animations["ForestSide1"].Setting(true, 0.3f);
                    animation.AddAnimation("ForestFrontAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Forest/ForestFrontAttack1"), 100, "ForestFrontAttack1"));
                    animation.animations["ForestFrontAttack1"].Setting(true, 0.3f);
                    animation.AddAnimation("ForestSideAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Forest/ForestSideAttack1"), 100, "ForestSideAttack1"));
                    animation.animations["ForestSideAttack1"].Setting(true, 0.3f);
                    animation.AddAnimation("ForestBackAttack1", new AnimationStrip(content.Load<Texture2D>("Character/Forest/ForestBackAttack1"), 100, "ForestBackAttack1"));
                    animation.animations["ForestBackAttack1"].Setting(true, 0.3f);

                    break;
                default:
                    break;
            }

            animation.PlayAnimation(name + "Front" + level.ToString());
        }

        #endregion


        #region Update & Draw

        public void Update(GameTime gameTime, int x, int y, Tile[,] tile, int tileHeight, int tileWidth, List<Enemy> enemy)
        {
            //화면 이동에 따른 위치 계산
            localLocation.X = worldLocation.X - x;
            localLocation.Y = worldLocation.Y - y;

            animation.updateAnimation(gameTime);
            HandleInput(tile, tileHeight, tileWidth, enemy);

            
            //캐릭터 이동 수치가 맞아떨어지지 않으면 지속적 이동현상
            if (isMove)
            {
                if (tileLocation == destinationTile)
                {
                    isMove = false;
                    isChangeRoute = false;

                    if (isTraceEnemy)
                    {
                        if (animation.CurrentAnimation == (name + "Front" + level.ToString()))
                        {
                            animation.PlayAnimation(name + "FrontAttack" + level.ToString());
                        }
                        if (animation.CurrentAnimation == (name + "Back" + level.ToString()))
                        {
                            animation.PlayAnimation(name + "BackAttack" + level.ToString());
                        }
                        if (animation.CurrentAnimation == (name + "Side" + level.ToString()))
                        {
                            animation.PlayAnimation(name + "SideAttack" + level.ToString());
                        }

                        target.getDamage(attack);
                    }
                    //isTraceEnemy = false;
                    return;
                }
                //이동 방향을 나타냄
                right = false;
                left = false;
                up = false;
                down = false;

                if (tileLocation.X - parent[0].TileLocation.X > 0)
                {
                    animation.PlayAnimation(name + "Back" + level.ToString());
                    animation.Flipped = false;
                    worldLocation.Y -= velocity;
                }
                else if (tileLocation.X - parent[0].TileLocation.X < 0)
                {
                    animation.PlayAnimation(name + "Front" + level.ToString());
                    animation.Flipped = false;
                    worldLocation.Y += velocity;
                }
                else if (tileLocation.Y - parent[0].TileLocation.Y > 0)
                {
                    animation.PlayAnimation(name + "Side" + level.ToString());
                    animation.Flipped = true;
                    worldLocation.X -= velocity;
                }
                else if (tileLocation.Y - parent[0].TileLocation.Y < 0)
                {
                    animation.PlayAnimation(name + "Side" + level.ToString());
                    animation.Flipped = false;
                    worldLocation.X += velocity;
                }

                int X = parent[0].TileLocation.X;
                int Y = parent[0].TileLocation.Y;
                int frameHeight = animation.animations[animation.CurrentAnimation].FrameWidth;
                int H = tile[X, Y].WorldLocation.X + (tile[X, Y].WorldLocation.Width - frameHeight) / 2;
                int W = tile[X, Y].WorldLocation.Y + tile[X, Y].WorldLocation.Height - frameHeight;

                if (worldLocation.X == H && worldLocation.Y == W)
                {
                    tile[tileLocation.X, tileLocation.Y].tileState = Tile.TileState.Empty;
                    tileLocation.X = parent[0].TileLocation.X;
                    tileLocation.Y = parent[0].TileLocation.Y;

                    SetTile(tile);
                    parent.RemoveAt(0);

                    //도착 조건
                    if (tile[tileLocation.X, tileLocation.Y].Destination)
                    {
                        isMove = false;
                        //isTraceEnemy = false;
                        isChangeRoute = false;

                        if (isTraceEnemy)
                        {
                            SetDestination(tile, target.TileLocation.X, target.TileLocation.Y, tileHeight, tileWidth);

                            if (parent.Count > 0)
                                isMove = true;
                            else
                            {
                                if (animation.CurrentAnimation == (name + "Front" + level.ToString()))
                                {
                                    animation.PlayAnimation(name + "FrontAttack" + level.ToString());
                                }
                                if (animation.CurrentAnimation == (name + "Back" + level.ToString()))
                                {
                                    animation.PlayAnimation(name + "BackAttack" + level.ToString());
                                }
                                if (animation.CurrentAnimation == (name + "Side" + level.ToString()))
                                {
                                    animation.PlayAnimation(name + "SideAttack" + level.ToString());
                                }

                                target.getDamage(attack);

                                return;
                            }
                        }
                    }
                    //적 추격시
                    if (isTraceEnemy)
                    {
                        SetDestination(tile, target.TileLocation.X, target.TileLocation.Y, tileHeight, tileWidth);
                    }
                    //루트 변경시
                    else if (isChangeRoute)
                    {
                        Explore(tile, tileHeight, tileWidth);
                        isChangeRoute = false;
                    }
                }
            }
            else
            {
                isChangeRoute = false;
                //isTraceEnemy = false;

                if (isTraceEnemy)
                {
                    SetDestination(tile, target.TileLocation.X, target.TileLocation.Y, tileHeight, tileWidth);

                    if (parent.Count > 0)
                        isMove = true;

                    if (!target.IsAlive)
                    {
                        isTraceEnemy = false;
                        target = null;

                        animation.PlayAnimation(name + "Front" + level.ToString());
                        animation.Flipped = false;
                    }
                }
            }
        }

        public void HandleInput(Tile[,] tile, int tileHeight, int tileWidth, List<Enemy> enemy)
        {
            Texture2D texture = animation.animations[animation.CurrentAnimation].Texture;
            int x = localLocation.X;
            int y = localLocation.Y;
            int w = animation.animations[animation.CurrentAnimation].Texture.Width;
            int h = animation.animations[animation.CurrentAnimation].Texture.Height;

            if (Collision.IntersectPixels(InputManager.MousePosition,
                        Collision.ExtractColor(InputManager.MouseTexture()),
                        new Rectangle(x, y, w, h), Collision.ExtractColor(texture)))
            {
                InputManager.ChangeMouseCursor("Select");
            }
            //캐릭터 선택
            if (Collision.IntersectPixels(InputManager.MousePosition,
                    Collision.ExtractColor(InputManager.MouseTexture()),
                    new Rectangle(x, y, w, h), Collision.ExtractColor(texture)))
            {
                if (InputManager.IsLeftClicked())
                {
                    isSelected = true;
                    foreach (Tile t in tile)
                        t.IsSelectCharacter = true;
                }
            }
            else
            {
                if (InputManager.IsLeftClicked())
                {
                    isSelected = false;

                    foreach (Tile t in tile)
                        t.IsSelectCharacter = false;
                }
            }
            

            if (isSelected)
            {
                //캐릭터 이동
                for (int i = 0; i < tileHeight; i++)
                {
                    for (int j = 0; j < tileWidth; j++)
                    {
                        if (tile[i, j].InScreen && 
                            tile[i, j].tileState != Tile.TileState.Enemy &&
                            tile[i, j].tileState != Tile.TileState.Block &&
                            tile[i, j].tileState != Tile.TileState.Tower)
                        {
                            if (InputManager.MousePosition.Intersects(tile[i, j].LocalLocation))
                            {
                                if (InputManager.IsRightClicked())
                                {
                                    tile[destinationTile.X, destinationTile.Y].Destination = false;
                                    destinationTile = new Point(i, j);
                                    tile[destinationTile.X, destinationTile.Y].Destination = true;

                                    if (isMove)
                                    {
                                        isTraceEnemy = false;
                                        isChangeRoute = true;
                                        return;
                                    }
                                    else
                                    {
                                        isMove = true;
                                        Explore(tile, tileHeight, tileWidth);
                                    }
                                    isTraceEnemy = false;
                                }
                            }
                        }
                    }
                }
                //적 추격 & 공격 명령
                foreach (Enemy e in enemy)
                {
                    x = e.LocalLocation.X;
                    y = e.LocalLocation.Y;
                    w = e.Animations.animations[e.Name + "Front"].Texture.Width;
                    h = e.Animations.animations[e.Name + "Front"].Texture.Height;
                    //더 정교하게 수정 필요
                    if (InputManager.MousePosition.Intersects(new Rectangle(x, y, w, h)))
                    {
                        InputManager.ChangeMouseCursor("Attack");

                        if (InputManager.IsRightClicked())
                        {
                            target = e;
                            isTraceEnemy = true;
                            isMove = true;

                            //SetDestination(tile, e.TileLocation.X, e.TileLocation.Y, tileHeight, tileWidth);

                            //destination을 적의 직전으로 설정(O)
                            //적을 공격하도록 설정
                            //적을 따라가도록 설정 (O) 
                        }
                    }
                }
            }
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            int x = localLocation.X;
            int y = localLocation.Y;
            int w = animation.animations[animation.CurrentAnimation].FrameWidth;
            int h = animation.animations[animation.CurrentAnimation].FrameHeight;

            animation.DrawAnimation(spriteBatch, new Rectangle(x, y, w, h));
        }

        #endregion


        #region Helper

        public void SelectCharacter(string attr)
        {
            switch(attr)
            {
                case "Fire":
                    attribute.attributeType = Attribute.AttributeType.Fire;
                    name = "Fire";
                    break;
                case "Water":
                    attribute.attributeType = Attribute.AttributeType.Water;
                    name = "Water";
                    break;
                case "Forest":
                    attribute.attributeType = Attribute.AttributeType.Forest;
                    name = "Forest";
                    break;
                default:
                    attribute.attributeType = Attribute.AttributeType.Fire;
                    name = "Fire";
                    break;
            }
        }

        public void UpgradeCharacter()
        {
            level++;

            switch (attribute.attributeType)
            {
                case Attribute.AttributeType.Fire:
                    animation.PlayAnimation("Fire" + level.ToString());
                    break;
                case Attribute.AttributeType.Water:
                    animation.PlayAnimation("Water" + level.ToString());
                    break;
                case Attribute.AttributeType.Forest:
                    animation.PlayAnimation("Forest" + level.ToString());
                    break;
                default:
                    animation.PlayAnimation("Fire" + level.ToString());
                    break;
            }
        }

        public void SetTile(Tile[,] tile)
        {
            //타일의 상태 변경
            tile[tileLocation.X, tileLocation.Y].tileState = Tile.TileState.Character;

            //캐릭터의 전체화면상 위치 - 캐릭터의 바닥과 타일의 바닥을 맞춤
            worldLocation.X = tile[tileLocation.X, tileLocation.Y].WorldLocation.X +
                (tile[tileLocation.X, tileLocation.Y].WorldLocation.Width -
                animation.animations[animation.CurrentAnimation].FrameWidth) / 2;
            worldLocation.Y = tile[tileLocation.X, tileLocation.Y].WorldLocation.Y +
                tile[tileLocation.X, tileLocation.Y].WorldLocation.Height - 
                animation.animations[animation.CurrentAnimation].FrameHeight;
        }

        public void SetDestination(Tile[,] tile, int x, int y, int tileHeight, int tileWidth)
        {
            tile[destinationTile.X, destinationTile.Y].Destination = false;
            destinationTile = new Point(x, y);
            tile[destinationTile.X, destinationTile.Y].Destination = true;
            Explore(tile, tileHeight, tileWidth);
            parent.RemoveAt(parent.Count - 1);

            if (parent.Count <= 0)
            {
                tile[destinationTile.X, destinationTile.Y].Destination = false;
                destinationTile = new Point(tileLocation.X, tileLocation.Y);
            }
            else
            {
                tile[destinationTile.X, destinationTile.Y].Destination = false;
                destinationTile = new Point(parent[parent.Count - 1].TileLocation.X, parent[parent.Count - 1].TileLocation.Y);
            }

            tile[destinationTile.X, destinationTile.Y].Destination = true;
        }
        //보완이 필요
        public void Explore(Tile[,] tile, int tileHeight, int tileWidth)
        {
            Tile current;
            List<Tile> open = new List<Tile>();
            List<Tile> close = new List<Tile>();

            int G = 0;

            parent.Clear();

            int startX = tileLocation.X;
            int startY = tileLocation.Y;
            int x = tileLocation.X;
            int y = tileLocation.Y;

            close.Add(tile[x, y]);
            current = tile[x, y];

            while (true)
            {
                G++;
                if (x - 1 >= 0)
                {
                    if (!open.Contains(tile[x - 1, y]) && !close.Contains(tile[x - 1, y]))
                    {
                        open.Add(tile[x - 1, y]);
                        CalculateFGH(tile, current, G, x - 1, y);
                    }
                }
                if (y - 1 >= 0)
                {
                    if (!open.Contains(tile[x, y - 1]) && !close.Contains(tile[x, y - 1]))
                    {
                        open.Add(tile[x, y - 1]);
                        CalculateFGH(tile, current, G, x, y - 1);
                    }
                }
                if (x + 1 < tileHeight)
                {
                    if (!open.Contains(tile[x + 1, y]) && !close.Contains(tile[x + 1, y]))
                    {
                        open.Add(tile[x + 1, y]);
                        CalculateFGH(tile, current, G, x + 1, y);
                    }
                }
                if (y + 1 < tileWidth)
                {
                    if (!open.Contains(tile[x, y + 1]) && !close.Contains(tile[x, y + 1]))
                    {
                        open.Add(tile[x, y + 1]);
                        CalculateFGH(tile, current, G, x, y + 1);
                    }
                }
                //종료 조건
                if(open.Contains(tile[destinationTile.X, destinationTile.Y]) || open.Count == 0)
                {
                    break;
                }


                for (int i = 1; i < open.Count + 1; i++ )
                {
                    if (open[open.Count - i].tileState == Tile.TileState.Block)
                    {
                        close.Add(open[open.Count - i]);
                        open.RemoveAt(open.Count - i);
                        i--;
                        continue;
                    }
                    if (open[open.Count - i].tileState == Tile.TileState.Tower)
                    {
                        close.Add(open[open.Count - i]);
                        open.RemoveAt(open.Count - i);
                        i--;
                        continue;
                    }
                    if (open[open.Count - i].tileState == Tile.TileState.Enemy)
                    {
                        close.Add(open[open.Count - i]);
                        open.RemoveAt(open.Count - i);
                        i--;
                        continue;
                    }
                }

                if (x - 1 >= 0 && y - 1 >= 0)
                {
                    if (open.Contains(tile[x - 1, y - 1]))
                    {
                        if (tile[x - 1, y - 1].G > G)
                        {
                            CalculateFGH(tile, current, G, x - 1, y - 1);
                        }
                    }
                }
                if (x + 1 < tileHeight && y - 1 >= 0)
                {
                    if (open.Contains(tile[x + 1, y - 1]))
                    {
                        if (tile[x + 1, y - 1].G > G)
                        {
                            CalculateFGH(tile, current, G, x + 1, y - 1);
                        }
                    }
                }
                if (x + 1 < tileHeight && y + 1 < tileWidth)
                {
                    if (open.Contains(tile[x + 1, y + 1]))
                    {
                        if (tile[x + 1, y + 1].G > G)
                        {
                            CalculateFGH(tile, current, G, x + 1, y + 1);
                        }
                    }
                }
                if (x - 1 >= 0 && y + 1 < tileWidth)
                {
                    if (open.Contains(tile[x - 1, y + 1]))
                    {
                        if (tile[x - 1, y + 1].G > G)
                        {
                            CalculateFGH(tile, current, G, x - 1, y + 1);
                        }
                    }
                }

                for (int j = 0; j < open.Count; j++)
                {
                    for (int i = 0; i < open.Count - j - 1; i++)
                    {
                        if (open[i].F < open[i + 1].F)
                        {
                            Tile temp = open[i];
                            open[i] = open[i + 1];
                            open[i + 1] = temp;
                        }
                    }
                }

                if (open.Count <= 0)
                    break;
                current = open[open.Count - 1];
                close.Add(current);
                open.Remove(current);

                x = current.TileLocation.X;
                y = current.TileLocation.Y;
            }

            current = tile[destinationTile.X, destinationTile.Y];
            while (true)
            {
                parent.Add(current);
                current = current.Parent;
                
                if (current.TileLocation.X == tileLocation.X &&
                    current.TileLocation.Y == tileLocation.Y)
                {
                    break;
                }
            }
            parent.Reverse(0, parent.Count);

        }

        public void CalculateFGH(Tile[,] tile, Tile current, int G, int x, int y)
        {
            int subX = Math.Abs(destinationTile.X - x);
            int subY = Math.Abs(destinationTile.Y - y);

            int H = subX + subY;
            int F = H + G;

            tile[x, y].G = G;
            tile[x, y].F = F;
            tile[x, y].Parent = current;
        }

        #endregion

    }
}
